/*
 * file_notify.c
 *
 *  Created on: Feb 21, 2011
 *      Author: thienlong
 */
#include "file_notify.h"
#include <dirent.h>
#include "log.h"
file_notify* file_notify_new(char *root,  int flags,
		int is_recursive, array_pool *pool) {
	file_notify *notify = malloc(sizeof(file_notify));
	notify->listener = inotify_init();
	evutil_make_socket_nonblocking(notify->listener);
	evutil_make_socket_closeonexec(notify->listener);
	notify->flags = flags;
	if (pool == NULL) {
		pool = malloc(sizeof(array_pool));
		apool_init(pool);
	}
	notify->pool = pool;
	notify->wd_dir
			= calloc(FILE_NOTIFY_CAPACITY_DEFAULT, sizeof(stringbuffer*));
	notify->capacity = FILE_NOTIFY_CAPACITY_DEFAULT;
	map_fn fns = {hash_stringbuffer, stringbuffer_is_equal, NULL, NULL};
	notify->dir_wd = hmap_new(&fns, NULL, 0);
	stringbuffer buf = apool_get(pool, STRING_BUFFER_TYPE);
	if (!buf) {
		buf = stringbuffer_new();
	}
	stringbuffer_copy_string(buf, root);
	notify->root_dir = root;
	if (is_recursive) {
		file_notify_add_watch_dirpath_recursive(notify, root, flags);
	} else {
		file_notify_add_watch_dirpath(notify, root, flags);
	}
	return notify;
}
void file_notify_free(file_notify* notify);

int file_notify_add_watch(file_notify* notify, int wd, char *dirname, int flags) {
	if (wd < 0 || wd >= notify->capacity || notify->wd_dir[wd] == NULL) {
		return -1;
	}
	stringbuffer *dirpath = lpool_get_element(notify->cache,
			STRING_BUFFER_TYPE);
	if (dirpath == NULL) {
		dirpath = stringbuffer_new();
	}
	stringbuffer_copy_stringbuffer(dirpath, notify->wd_dir[wd]);
	stringbuffer_append_string(dirpath, dirname);
	stringbuffer_append_char(dirpath, '/');
	int res = file_notify_add_watch_dirpath_reference(notify, &dirpath, flags);
	if (dirpath != NULL) {
		lpool_put_element(notify->cache, STRING_BUFFER_TYPE,
				(struct next_data*) dirpath);
	}
	return res;
}

int file_notify_add_watch_recursive(file_notify* notify, int wd, char *dirname,
		int flags) {
	if (wd < 0 || wd >= notify->capacity || notify->wd_dir[wd] == NULL) {
		return -1;
	}
	stringbuffer *dirpath = lpool_get_element(notify->cache,
			STRING_BUFFER_TYPE);
	if (dirpath == NULL) {
		dirpath = stringbuffer_new();
	}
	stringbuffer_copy_stringbuffer(dirpath, notify->wd_dir[wd]);
	stringbuffer_append_string(dirpath, dirname);
	stringbuffer_append_char(dirpath, '/');
	int res = file_notify_add_watch_dirpath_recursive(notify, dirpath->ptr,
			flags);
	lpool_put_element(notify->cache, STRING_BUFFER_TYPE,
			(struct next_data*) dirpath);
	return res;
}

int file_notify_add_watch_dirpath(file_notify* notify, char *dirpath, int flags) {
	int wd = inotify_add_watch(notify->listener, dirpath, flags);
	if (wd == -1) {
		return -1;
	} else if (notify->wd_dir[wd] != NULL) {
		return 0;
	} else if (wd >= notify->capacity) {
		int new_capacity = notify->capacity * (wd / notify->capacity + 1);
		notify->wd_dir = realloc(notify->wd_dir, new_capacity
				* sizeof(stringbuffer*));
		memset(notify->wd_dir + notify->capacity, 0, (new_capacity
				- notify->capacity) * sizeof(stringbuffer*));
		notify->capacity = new_capacity;
	}
	stringbuffer *dir = lpool_get_element(notify->cache,
			STRING_BUFFER_TYPE);
	if (dir == NULL) {
		dir = stringbuffer_new();
	}
	stringbuffer_copy_string(dir, dirpath);
	notify->wd_dir[wd] = dir;
	int *value = malloc(sizeof(int));
	*value = wd;
//	hashmap_put(notify->cache, notify->dir_wd, dir, value);
	return 0;
}

int file_notify_add_watch_dirpath_reference(file_notify* notify,
		stringbuffer **dirpath, int flags) {
	int wd = inotify_add_watch(notify->listener, (*dirpath)->ptr, flags);
	if (wd == -1) {
		return -1;
	} else if (notify->wd_dir[wd] != NULL) {
		return 0;
	} else if (wd >= notify->capacity) {
		int new_capacity = notify->capacity * (wd / notify->capacity + 1);
		notify->wd_dir = realloc(notify->wd_dir, new_capacity
				* sizeof(stringbuffer*));
		memset(notify->wd_dir + notify->capacity, 0, (new_capacity
				- notify->capacity) * sizeof(stringbuffer*));
		notify->capacity = new_capacity;
	}
	notify->wd_dir[wd] = *dirpath;
	int *value = malloc(sizeof(int));
	*value = wd;
//	hashmap_put(notify->cache, notify->dir_wd, *dirpath, value);
	*dirpath = NULL;
	return 0;
}

int file_notify_add_watch_dirpath_recursive(file_notify* notify, char *dirpath,
		int flags) {
	int res = file_notify_add_watch_dirpath(notify, dirpath, flags);
	if (res == -1) {
		return -1;
	}
	stringbuffer_array *dirs = lpool_get_element(notify->cache,
			STRING_BUFFER_ARRAY_TYPE);
	if (dirs == NULL) {
		dirs = stringbuffer_array_new();
	}
	stringbuffer *dir_str = stringbuffer_array_append_get_stringbuffer(dirs);
	stringbuffer_copy_string(dir_str, dirpath);
	stringbuffer *new_dir_str = NULL;
	DIR *dir;
	struct dirent *ep;
	int i = 0;
	while (i < dirs->used) {
		dir_str = dirs->ptr[i];
		dir = opendir(dir_str->ptr);
		if (dir == NULL) {
			fprintf_log(LOG_ERROR,
					"[%s] In %s() %d: Can't open directory: \"%s\"", __FILE__,
					__FUNCTION__, __LINE__, notify->root_dir->ptr);
			continue;
		}

		while ((ep = readdir(dir)) != NULL) {
			if (ep->d_type == DT_DIR && strcmp(ep->d_name, ".") != 0 && strcmp(
					ep->d_name, "..") != 0) {
				new_dir_str = stringbuffer_array_append_get_stringbuffer(dirs);
				stringbuffer_copy_stringbuffer(new_dir_str, dir_str);
				stringbuffer_append_string(new_dir_str, ep->d_name);
				stringbuffer_append_char(new_dir_str, '/');
				file_notify_add_watch_dirpath(notify, new_dir_str->ptr, flags);
			}
		}
		(void) closedir(dir);
		++i;
	}
	lpool_put_element(notify->cache, STRING_BUFFER_ARRAY_TYPE,
			(struct next_data*) dirs);
	return 0;
}

int file_notify_replace_wath_dirname(file_notify* notify, int moved_wd,
		char* moved_dirname, int dst_wd, char* dst_dirname) {
	return -1;
}

int file_notify_rm_watch(file_notify* notify, int wd) {
	if (wd < 0 || wd >= notify->capacity || notify->wd_dir[wd] == NULL) {
		return -1;
	}
//	int *value = hashmap_remove(notify->cache, notify->dir_wd,
//			notify->wd_dir[wd]);
//	free(value);
//	notify->wd_dir[wd] = NULL;
//	return inotify_rm_watch(notify->listener, wd);
}

int file_notify_rm_watch_dirpath(file_notify *notify, char *dirpath) {
//	int *value = hashmap_remove_char(notify->cache, notify->dir_wd, dirpath);
//	if (value) {
//		int wd = *value;
//		free(value);
//		notify->wd_dir[wd] = NULL;
//		return inotify_rm_watch(notify->listener, wd);
//	}
//	return 0;
}

//FIXME
int file_notify_rm_watch_dirpath_recursive(file_notify *notify, char *dirpath) {
	int res = file_notify_rm_watch_dirpath(notify, dirpath);
	if (res == -1) {
		return -1;
	}
	stringbuffer_array *dirs = lpool_get_element(notify->cache,
			STRING_BUFFER_ARRAY_TYPE);
	if (dirs == NULL) {
		dirs = stringbuffer_array_new();
	}
	stringbuffer *dir_str = stringbuffer_array_append_get_stringbuffer(dirs);
	stringbuffer_copy_string(dir_str, dirpath);
	stringbuffer *new_dir_str = NULL;
	DIR *dir;
	struct dirent *ep;
	int i = 0;
	while (i < dirs->used) {
		dir_str = dirs->ptr[i];
		dir = opendir(dir_str->ptr);
		if (dir == NULL) {
			fprintf_log(LOG_ERROR,
					"[%s] In %s() %d: Can't open directory: \"%s\"", __FILE__,
					__FUNCTION__, __LINE__, notify->root_dir->ptr);
			continue;
		}

		while ((ep = readdir(dir)) != NULL) {
			if (ep->d_type == DT_DIR && strcmp(ep->d_name, ".") != 0 && strcmp(
					ep->d_name, "..") != 0) {
				new_dir_str = stringbuffer_array_append_get_stringbuffer(dirs);
				stringbuffer_copy_stringbuffer(new_dir_str, dir_str);
				stringbuffer_append_string(new_dir_str, ep->d_name);
				stringbuffer_append_char(new_dir_str, '/');
				file_notify_rm_watch_dirpath(notify, new_dir_str->ptr);
			}
		}
		(void) closedir(dir);
		++i;
	}
	lpool_put_element(notify->cache, STRING_BUFFER_ARRAY_TYPE,
			(struct next_data*) dirs);
	return 0;
}

int file_notify_rm_watch_dirname(file_notify *notify, int wd, char *dirname) {
	if (wd < 0 || wd >= notify->capacity || notify->wd_dir[wd] == NULL) {
		return -1;
	}
	stringbuffer *dirpath = lpool_get_element(notify->cache,
			STRING_BUFFER_TYPE);
	if (dirpath == NULL) {
		dirpath = stringbuffer_new();
	}
	stringbuffer_copy_stringbuffer(dirpath, notify->wd_dir[wd]);
	stringbuffer_append_string(dirpath, dirname);
	stringbuffer_append_char(dirpath, '/');
	int res = file_notify_rm_watch_dirpath(notify, dirpath->ptr);
	lpool_put_element(notify->cache, STRING_BUFFER_TYPE,
			(struct next_data*) dirpath);
	return res;
}

int file_notify_rm_watch_dirname_recursive(file_notify *notify, int wd,
		char *dirname) {
	if (wd < 0 || wd >= notify->capacity || notify->wd_dir[wd] == NULL) {
		return -1;
	}
	stringbuffer *dirpath = lpool_get_element(notify->cache,
			STRING_BUFFER_TYPE);
	if (dirpath == NULL) {
		dirpath = stringbuffer_new();
	}
	stringbuffer_copy_stringbuffer(dirpath, notify->wd_dir[wd]);
	stringbuffer_append_string(dirpath, dirname);
	stringbuffer_append_char(dirpath, '/');
	int res = file_notify_rm_watch_dirpath_recursive(notify, dirpath->ptr);
	lpool_put_element(notify->cache, STRING_BUFFER_TYPE,
			(struct next_data*) dirpath);
	return res;
}

char* file_notify_get_dirpath(file_notify *notify, int wd) {
	if (wd < 0 || wd >= notify->capacity || notify->wd_dir[wd] == NULL) {
		return NULL;
	}
	return notify->wd_dir[wd]->ptr;
}

int file_notify_get_wd(file_notify *notify, char *dirpath) {
//	int *value = hashmap_get_char(notify->dir_wd, dirpath);
//	return (value == NULL) ? -1 : *value;
}
